﻿using Rsdn.Janus.CoreServices.NavigationTree;

namespace Rsdn.Janus
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using BLToolkit.Data;
    using DataModel;
    using Framework;
    using JetBrains.Annotations;
    using ObjectModel;
    using SmartApp;
    using TreeGrid;
    using VirtualForums.AnswersMe;
    using VirtualForums.AnswersMy;
    using VirtualForums.NewMessages;

    internal sealed class Forums : FolderFeature, IFeature, IGetData
    {
        public const int VirtualForum = 0x7FFF0000;
        public const int VirtualForumAnswersMeId = VirtualForum | 0x1;
        public const int VirtualForumMyAnswersId = VirtualForum | 0x2;
        public const int VirtualForumNewMessages = VirtualForum | 0x4;

        private readonly IServiceProvider _serviceProvider;
        private readonly IJanusDatabaseManager _dbManager;
        private readonly SmartApp.Lazy<IForumsAggregatesService> _forumsAggregatesService;
        private readonly List<ForumFolder> _folders = new List<ForumFolder>();

        /// <summary>
        /// place for forums which was subscribed but unsubscribed now.
        /// </summary>
        private readonly ForumFolder _unsubscribedFolder;
        private readonly AnswersMeFeature _answersMeFolder;
        private readonly MyAnswersFeature _answersMyFolder;
        private readonly NewMessagesFeature _newMessagesFolder;

        private Dictionary<int, Forum> _forumsIdHash = new Dictionary<int, Forum>();
        private List<IDisposable> _forumLiveBehaviors;
		private volatile static Forums _instance;

        private Forum[] forumList;

        private IVirtualForumTreeService virtualForumTreeService;

        #region События

        /// <summary>
        /// Вызывается перед загрузкой данных из БД.
        /// </summary>
        public static event EventHandler OnBeforeLoadData;

        #endregion События

        /// <summary>
        /// Initializes a new instance of the <see cref="Forums"/> class.
        /// </summary>
        /// <param name="provider">The provider.</param>
        private Forums(IServiceProvider provider)
            : base(provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            _serviceProvider = provider;
            _dbManager = ServiceProvider.GetRequiredService<IJanusDatabaseManager>();
            _forumsAggregatesService = new SmartApp.Lazy<IForumsAggregatesService>(
                () => ServiceProvider.GetRequiredService<IForumsAggregatesService>());

            _unsubscribedFolder = new ForumFolder(ServiceProvider, this, SR.Forum.Unsubscribed.DisplayName);
            _answersMeFolder = new AnswersMeFeature(ServiceProvider);
            _answersMyFolder = new MyAnswersFeature(ServiceProvider);
            _newMessagesFolder = new NewMessagesFeature(ServiceProvider);
            _folders.Add(_unsubscribedFolder);
            //_folders.Add(_answersMeFolder);
            _flags = NodeFlags.AutoExpand;
            _description = SR.Forum.Incoming.DisplayName;

            virtualForumTreeService = ServiceProvider.GetRequiredService<IVirtualForumTreeService>();
            LoadData();
            _answersMeFolder.Refresh();
            _answersMyFolder.Refresh();
            _newMessagesFolder.Refresh();
        }

        public static Forums Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (typeof(Forums))
                    {
                        if (_instance == null)
                        {
                            _instance = new Forums(ApplicationManager.Instance.ServiceProvider);
                        }
                    }
                }

                return _instance;
            }
        }

        // ReSharper disable MemberCanBeMadeStatic.Global
        [CanBeNull]
        public Forum ActiveForum
        // ReSharper restore MemberCanBeMadeStatic.Global
        {
            get
            {
                return ObjectModel.Features.Instance.ActiveFeature as Forum;
            }

            set
            {
                ObjectModel.Features.Instance.ActiveFeature = value;
            }
        }

        public Forum[] ForumList
        {
            get
            {
                return forumList;
            }

            private set
            {
                forumList = value;
            }
        }

        public ForumFolder UnsubscribedForums
        {
            get
            {
                return _unsubscribedFolder;
            }
        }


        /// <summary>
        /// Проверяет, находится ли форум, указанный с помощью <paramref name="forum"/> в списке подписанных форумов.
        /// </summary>
        /// <param name="forum">Проверяемый форум.</param>
        /// <returns><c>true</c>, если форум находится в списке подписанных форумов, иначе <c>false</c>.</returns>
        public bool IsSubscribed(Forum forum)
        {
            if (forum == null)
            {
                throw new ArgumentNullException("forum");
            }

            return Array.IndexOf(ForumList, forum) >= 0;
        }

        private void InvokeBeforeLoadData()
        {
            EventHandler handler = OnBeforeLoadData;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        #region Интерфейс коллекции.

        //IFeature
        string IFeature.Key
        {
            get
            {
                return "Inbox";
            }
        }

        public override string Info
        {
            get
            {
                return ForumList != null
                        ? (Config.Instance.ForumDisplayConfig.ShowTotalMessages ? " {0}/{1}/{2}" : " {0}/{1}")
                            .FormatStr(
                            _forumsAggregatesService.Value.UnreadRepliesToMeCount,
                            _forumsAggregatesService.Value.UnreadMessagesCount,
                            _forumsAggregatesService.Value.MessagesCount)
                        : string.Empty;
            }
        }

        public override int ImageIndex
        {
            get
            {
                return ObjectModel.Features.Instance.ForumsImageIndex;
            }
        }

        public override bool IsUpdateNeed(object msgIn)
        {
            if (msgIn is Msg)
            {
                Msg msg= msgIn as Msg;
            }

            // It was wish to check if msg included in current forum but forum implented in an other way
            return true;
        }

        public Forum this[int forumId]
        {
            get
            {
                return _forumsIdHash[forumId];
            }
        }

        IFeature IFeature.this[int forumId]
        {
            get
            {
                return this[forumId];
            }
        }

        ITreeNode ITreeNode.this[int index]
        {
            get
            {
                if (index < ForumList.Length)
                {
                    return ForumList[index];
                }

                return _folders[index - ForumList.Length];
            }
        }

        bool ITreeNode.HasChildren
        {
            get
            {
                return (ForumList != null && ForumList.Length > 0) || _folders.Count > 0;
            }
        }

        // ICollection
        public int Count
        {
            get
            {
                int ret = 0;
                if (ForumList != null && _folders != null)
                {
                    ret = ForumList.Length + _folders.Count;
                }
                return ret;
            }
        }

        public bool IsSynchronized
        {
            get
            {
                return ForumList.IsSynchronized;
            }
        }

        public object SyncRoot
        {
            get
            {
                return typeof(Forums);
            }
        }

        public IServiceProvider ServiceProvider
        {
            get { return _serviceProvider; }
        }

        public void CopyTo(Array array, int index)
        {
            ForumList.CopyTo(array, index);
        }

        // IEnumerable
        public IEnumerator GetEnumerator()
        {
            return new TwoCollectionEnumerator(ForumList, _folders);
        }
        #endregion

        #region Работа с БД.
        /// <summary>
        /// Loads the subscribed forums from database.
        /// </summary>
        /// <param name="db">The db.</param>
        /// <returns></returns>
        private IEnumerable<Forum> LoadSubscribedForums(IJanusDataContext db) // after program start & Forums.Refresh()
        {
            IOrderedQueryable<ISubscribedForum> subscribedForumsOrderByPriority = db.SubscribedForums().OrderByDescending(f => f.Priority);

            var tableSubscribedForums =
                Config.Instance.ForumDisplayConfig.ShowFullForumNames
                    ? subscribedForumsOrderByPriority.ThenBy(f => f.Descript)
                    : subscribedForumsOrderByPriority.ThenBy(f => f.Name);

            var countsCalculator =
                from topicInfo in db.TopicInfos() // Table: topic_info
                group topicInfo by topicInfo.ForumID into groupingStorage
                select
                    new
                    {
                        ForumID = groupingStorage.Key,
                        Count = groupingStorage.Count() + groupingStorage.Sum(iti => iti.AnswersCount),
                        Unread = groupingStorage.Sum(iti => iti.AnswersUnread),
                        MeUnread = groupingStorage.Sum(iti => iti.AnswersToMeUnread)
                    };

            IQueryable<Forum> forums =
                from subscribedForums in tableSubscribedForums
                join ti in countsCalculator on subscribedForums.ID equals ti.ForumID into leftJoinStorage
                from messagesCount in leftJoinStorage.DefaultIfEmpty() // left join forcing
                select
                    new Forum(ServiceProvider)
                    {
                        ID = subscribedForums.ID,
                        Name = subscribedForums.Name,
                        LastSync = subscribedForums.LastSync,
                        IsSubscribed = true,
                        IsRateable = subscribedForums.ServerForum.Rated,
                        RateLimit = subscribedForums.ServerForum.RateLimit > 0,
                        MessagesCount = messagesCount.Count,
                        RepliesToMeUnread = messagesCount.MeUnread,
                        Unread = messagesCount.Unread,
                        InTop = subscribedForums.ServerForum.InTop,
                        Priority = subscribedForums.Priority.GetValueOrDefault(),
                        ForumDescription = subscribedForums.Descript
                    };
            #region SQL
            //--  SQLite
            //SELECT
            //    [f].[ID],
            //    [f].[Name],
            //    [f].[LastSync],
            //    [t1].[Rated],
            //    [t1].[RateLimit],
            //    [t2].[Count1] as [Count11],
            //    [t2].[MeUnread] as [MeUnread1],
            //    [t2].[Unread] as [Unread1],
            //    [t1].[InTop],
            //    [f].[Priority],
            //    [f].[Descript]
            //FROM
            //    [subscribed_forums] [f]
            //        LEFT JOIN [server_forums] [t1] ON [f].[ID] = [t1].[ID]
            //        LEFT JOIN (
            //            SELECT
            //                [topicInfo].[gid] as [ForumID],
            //                Count(*) + Sum([topicInfo].[answers_count]) as [Count1],
            //                Sum([topicInfo].[answers_me_unread]) as [MeUnread],
            //                Sum([topicInfo].[answers_unread]) as [Unread]
            //            FROM
            //                [topic_info] [topicInfo]
            //            GROUP BY
            //                [topicInfo].[gid]
            //        ) [t2] ON [f].[ID] = [t2].[ForumID]
            //ORDER BY
            //    [f].[Priority] DESC,
            //    [f].[Descript]


            #endregion
            return forums.ToList();
        }

        private IEnumerable<Forum> LoadServerForums(IJanusDataContext db)
        {
            var serverForumsOriginalSort = db.ServerForums().OrderByDescending(f => f.SubscribedForum.Priority);
            var serverForums =
                Config.Instance.ForumDisplayConfig.ShowFullForumNames
                    ? serverForumsOriginalSort.ThenBy(f => f.Descript)
                    : serverForumsOriginalSort.ThenBy(f => f.Name);

            var counts =
                from ti in db.TopicInfos()
                group ti by ti.ForumID into grp
                select
                    new
                    {
                        ForumID = grp.Key,
                        Count = grp.Count() + grp.Sum(iti => iti.AnswersCount),
                        Unread = grp.Sum(iti => iti.AnswersUnread),
                        MeUnread = grp.Sum(iti => iti.AnswersToMeUnread)
                    };

            var forums =
                from f in serverForums
                join ti in counts on f.ID equals ti.ForumID into leftJoinStorage
                from messagesCount in leftJoinStorage.DefaultIfEmpty() // left join forcing
                where messagesCount.Count > 0
                select
                    new Forum(ServiceProvider)
                    {
                        ID = f.ID,
                        Name = f.Name,
                        LastSync = f.SubscribedForum.LastSync,
                        IsSubscribed = false,
                        IsRateable = f.Rated,
                        RateLimit = f.RateLimit > 0,
                        MessagesCount = messagesCount.Count,
                        RepliesToMeUnread = messagesCount.MeUnread,
                        Unread = messagesCount.Unread,
                        InTop = f.InTop,
                        Priority = f.SubscribedForum.Priority.GetValueOrDefault(),
                        ForumDescription = f.Descript
                    };
            return forums.Cast<Forum>().ToList();
        }

        // TODO: Intensive refactoring required!
        private void LoadData()
        {
            using (_dbManager.GetLock().GetWriterLock())
            {
			using (IJanusDataContext db = _dbManager.CreateDBContext())
                {
                    if (!db.IsValid())
                    {
                        return;
                    }

                    InvokeBeforeLoadData();
                    //if (OnBeforeLoadData != null)
                    //{
                    //    OnBeforeLoadData(this, EventArgs.Empty);
                    //}

                    var liveBehaviors = new List<IDisposable>();

                    var newIdHash = new Dictionary<int, Forum>();
                
                    IEnumerable<Forum> subscribedForums = LoadSubscribedForums(db);
                    foreach (Forum forum in subscribedForums)
                    {
                        forum._parent = this;
                        liveBehaviors.Add(forum.ActivateLiveBehavior());

                        newIdHash[forum.ID] = forum;
                    }

                    IEnumerable<Forum> serverForums = LoadServerForums(db);
                    List<Forum> unsubscribedForums = new List<Forum>();
                    foreach (Forum forum in serverForums)
                    {
                        if (newIdHash.ContainsKey(forum.ID))
                        {
                            continue;
                        }

                        liveBehaviors.Add(forum.ActivateLiveBehavior());

                        unsubscribedForums.Add(forum);
                        newIdHash.Add(forum.ID, forum);
                    }

                    var oldForums = new List<Forum>(_unsubscribedFolder.Cast<Forum>());

                    if (ForumList != null)
                    {
                        oldForums.AddRange(ForumList);
                    }

                    List<Forum> dynForums = new List<Forum>(subscribedForums);

                    AddVirtualForums(dynForums);
                    //LoadVirtualForums(); // don't need anymore

                    foreach (Forum dynForum in dynForums)
                    {
                        if (!newIdHash.ContainsKey(dynForum.ID))
                        {
                            newIdHash.Add(dynForum.ID, dynForum);
                        }
                    }

                    ForumList = dynForums.ToArray();

                    _unsubscribedFolder.Clear();

                    foreach (Forum forum in unsubscribedForums)
                    {
                        _unsubscribedFolder.Add(forum);
                    }

                    _forumsIdHash = newIdHash;

                    if (_forumLiveBehaviors != null)
                    {
                        _forumLiveBehaviors.DisposeAll();
                    }
                    _forumLiveBehaviors = liveBehaviors;
                }
            }
        }

        /// <summary>
        /// Adds the virtual forums.
        /// </summary>
        /// <param name="dynForums">The existing forums.</param>
        private void AddVirtualForums(List<Forum> dynForums)
        {
            foreach (IVirtualForumProvider virtualForumProvider in virtualForumTreeService.GetVirtualForums())
            {
                Rsdn.Janus.VirtualForum virtualForum = new VirtualForum(ServiceProvider, virtualForumProvider);
                virtualForum.Clear();
                virtualForum.Refresh();
                dynForums.Add(virtualForum);
            }

            //_answersMeFolder.Clear();
            //dynForums.Add(_answersMeFolder);

            //_answersMyFolder.Clear();
            //dynForums.Add(_answersMyFolder);

            //_newMessagesFolder.Clear();
            //dynForums.Add(_newMessagesFolder);
        }

        /// <summary>
        /// Обновляет содержимое 
        /// </summary>
        public override void Refresh()// after reading Db
        {
            LoadData(); // here loaded all forum counters at once
            if (ForumList != null)
            {
                foreach (Forum forum in ForumList)
                {
                    // active feature already refreshed in Features
                    IFeature activeFeature = ObjectModel.Features.Instance.ActiveFeature;
                    if (activeFeature == null || activeFeature.Key != forum.Key)
                    {
                        forum.Refresh();
                    }
                }
            }
        }

        #endregion

        #region IGetData Members

        public void GetData(NodeInfo nodeInfo, CellInfo[] cellData)
        {
            nodeInfo.ForeColor = Config.Instance.StyleConfig.NavigationTreeForumColor;
            nodeInfo.Highlight = _forumsAggregatesService.Value.UnreadMessagesCount > 0;

            cellData[0].Text = Description;
            cellData[0].ImageIndex = ObjectModel.Features.Instance.ForumsImageIndex;
            cellData[1].Text = Info;
        }

        #endregion
    }
}

// Here names is something different
// 'forum can contain a number of subforums, each of which may have several topics. 
// Within a forum's topic, each new discussion started is called a thread'
// and can be replied to by as many people as wish to.
